package com.hwtx.form.domain.handle.datamodel;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.ds.DataSourceContextHolder;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.vo.DataModelDetailsDto;
import com.hwtx.form.persistence.DataSourceRepo;
import com.hwtx.form.persistence.SQLType;
import com.hwtx.form.persistence.ds.metadata.Column;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.QueryParser;
import com.hwtx.form.util.QueryParserResult;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.framework.page.Paging;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.Serial;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static com.hwtx.form.domain.FormConstants.DEFAULT_FIELDS;
import static com.hwtx.form.util.QueryParserResult.SelectType.STR;

/**
 * <p>
 * 表单定义明细表
 * </p>
 *
 * @author baomidou
 * @since 2024-07-07
 */
@Getter
@Setter
@TableName(value = "data_model")
@Slf4j
public class DataModel implements Serializable {

    public static final String TABLE_TYPE_SINGLE = "1";
    public static final String TABLE_TYPE_AGGREGATE = "2";

    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 名称
     */
    @TableField("name")
    private String name;
    /**
     * 描述
     */
    @TableField("description")
    private String description;
    /**
     * 数据源
     */
    @TableField("ds")
    private String ds;
    /**
     * 类型
     */
    @TableField("type")
    private String type;
    /**
     * 主表及关联表
     */
    @TableField("master")
    private String master;
    /**
     * 模型类型（1：数据表模型，2：sql模型）
     */
    @TableField("model_type")
    private String modelType;

    @TableField("sql_content")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private String sqlContent;
    /**
     * 数据表类型（1：单一模型，2：聚合模型）
     */
    @TableField("table_type")
    private String tableType;
    /**
     * 过滤配置
     */
    @TableField("outer_filter")
    private String outerFilter;
    /**
     * 创建时间
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    @JsonIgnore
    private Date createTime;
    /**
     * 创建人ID
     */
    @TableField(value = "create_id", fill = FieldFill.INSERT)
    @JsonIgnore
    private String createId;

    /**
     * 修改时间
     */
    @TableField(value = "update_time", fill = FieldFill.UPDATE)
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;

    /**
     * 更新人ID
     */
    @TableField(value = "update_id", fill = FieldFill.UPDATE)
    @JsonIgnore
    private String updateId;

    public static DataModelDetailsDto buildDataModelDetail(DataModelBuildInfo buildInfo) {
        String key = DataSourceContextHolder.getDataSourceKey();
        DataSourceContextHolder.setDefaultDsKey();
        return FormUtil.dataSourceInvoke(DataSourceContextHolder.getDefaultDs(), () -> {
            DataModelDetailsDto dto = new DataModelDetailsDto();
            DataModelRepo dataModelRepo = SpringUtil.getBean(DataModelRepo.class);
            DataSourceRepo dataSourceRepo = SpringUtil.getBean(DataSourceRepo.class);
            DataModel dataModel = null;
            if (StringUtils.isBlank(buildInfo.getDs()) && StringUtils.isBlank(buildInfo.getSql())) {
                throw new BusinessException("数据源不能为空");
            }
            if (StringUtils.isNotBlank(buildInfo.getModelName())) {
                dataModel = dataModelRepo.getDataModel(buildInfo.getModelName());
            }
            if (dataModel == null || !Objects.equals(buildInfo.getMaster(), dataModel.getMaster()) || !Objects.equals(dataModel.getDs(), buildInfo.getDs())) {
                List<DataModelDetail> data = dataModelDetailsFromDb(buildInfo.getMaster(), buildInfo.getDs(), buildInfo.getModelName(), dataSourceRepo);
                if (buildInfo.isPage()) {
                    int index = buildInfo.getPageIndex() - 1;
                    int start = index * buildInfo.getPageSize();
                    int end = start + buildInfo.getPageSize();
                    if (end > data.size()) {
                        end = data.size();
                    }
                    dto.setPageData(new Paging<>(data.subList(start, end), data.size(), buildInfo.getPageIndex(), buildInfo.getPageSize()));
                } else {
                    dto.setData(data);
                }
                dto.setSyn(CollectionUtils.isNotEmpty(data));
                dto.setAdd(CollectionUtils.isEmpty(data));
            } else {
                if (buildInfo.isPage()) {
                    dto.setPageData(dataModelRepo.pageDeDetails(buildInfo.getPageIndex(), buildInfo.getPageSize(), dataModel.getModelName(), FormConstants.Status.VALID));
                } else {
                    dto.setData(dataModelRepo.getDetails(dataModel.getModelName(), FormConstants.Status.VALID));
                }
                dto.setInit(!dataModelRepo.hasFields(dataModel.getModelName(), FormConstants.DefaultColumn.getDefaultColumns().toArray()));
                dto.setAdd(true);
                if (StringUtils.isNotBlank(dataModel.getMaster())) {
                    DataSourceContextHolder.setDataSourceKey(key);
                    boolean existTable = dataSourceRepo.exist(dataModel.getMaster());
                    if (existTable) {
                        dto.setSyn(true);
                        if (!dataSourceRepo.hasData(dataModel.getMaster())) {
                            dto.setCanOp(true);
                        }
                    } else {
                        if (buildInfo.isPage() ? dto.getPageData().getItems().isEmpty() : dto.getData().isEmpty()) {
                            dto.setInit(true);
                        } else {
                            dto.setSyn(true);
                        }
                    }
                }
            }
            return dto;
        });
    }

    public static List<DataModelDetail> dataModelDetailsFromDb(String table, String modelName, String ds, DataSourceRepo dataSourceRepo) {
        AtomicInteger num = new AtomicInteger(1);
        List<String> uniqueColumns = dataSourceRepo.getConstraintColumns(table);
        return FormUtil.dataSourceInvoke(ds, () -> dataSourceRepo.getColumns(table).entrySet().stream().map(entry -> {
            String name = entry.getKey();
            Column column = entry.getValue();
            return buildDataModelDetailByColumn(table, name, column, num, modelName, uniqueColumns);
        }).toList());
    }

    private static List<DataModelDetail> buildDataModelDetailByTable(String table, String modelName) {
        DataSourceRepo dataSourceRepo = SpringUtil.getBean(DataSourceRepo.class);
        Map<String, Column> columns = dataSourceRepo.getColumns(table);
        AtomicInteger num = new AtomicInteger(1);
        List<String> uniqueColumns = dataSourceRepo.getConstraintColumns(table);
        return columns.entrySet().stream().map(entry -> buildDataModelDetailByColumn(table, entry.getKey(), entry.getValue(), num, modelName, uniqueColumns)).toList();
    }

    public static DataModelDetail buildDataModelDetailByColumn(String table, String name, Column column, AtomicInteger num, String modelName, List<String> uniqueColumns) {
        DataModelDetail dataModelDetail = new DataModelDetail();
        dataModelDetail.setNum(num.getAndIncrement());
        dataModelDetail.setTableName(table);
        dataModelDetail.setName(name);
        dataModelDetail.setModelName(modelName);
        dataModelDetail.setStatus(FormConstants.Status.VALID.getCode());
        dataModelDetail.setCanOp((byte) 0);
        if (column == null) {
            return dataModelDetail;
        }
        if (DEFAULT_FIELDS.containsKey(name)) {
            dataModelDetail.setSys((byte) 1);
        }
        dataModelDetail.setType(SQLType.getType(column.getTypeName()));
        dataModelDetail.setLength(column.getPrecision());
        dataModelDetail.setDescription(column.getComment());
        if (column.isNullable() != -1) {
            dataModelDetail.setNotNull(column.isNullable() == 1 ? (byte) 0 : (byte) 1);
        }
        if (column.getScale() != null) {
            dataModelDetail.setPoints(column.getScale());
        }
        dataModelDetail.setPriKey(column.isPrimaryKey() == 1 ? (byte) 1 : (byte) 0);
        if (column.getDefaultValue() != null) {
            dataModelDetail.setDefValue(column.getDefaultValue().toString());
        }
        if (uniqueColumns != null && uniqueColumns.contains(name)) {
            dataModelDetail.setIsUnique((byte) 1);
        }
        if (Objects.equals(dataModelDetail.getPriKey(), (byte) 1)) {
            dataModelDetail.setIsUnique((byte) 1);
        }
        return dataModelDetail;
    }

    public static List<DataModelDetail> parserAndBuildDetails(DataModelBuildInfo buildInfo, DataSourceRepo dataSourceRepo) {
        String sql = buildInfo.getSql();
        String modelName = buildInfo.getModelName();
        List<DataModelDetail> details = new ArrayList<>();
        try {
            QueryParserResult parserResult = QueryParser.parser(sql);
            QueryParserResult.Project project = parserResult.getProject();
            if (project == null) {
                return details;
            }
            Map<String, String> allTableAndAlias = Maps.newHashMap();
            Map<String, List<String>> tableAndUnique = Maps.newHashMap();
            allTableAndAlias.put(parserResult.getTable().getName(), parserResult.getTable().getAlias());
            tableAndUnique.put(parserResult.getTable().getName(), dataSourceRepo.getConstraintColumns(parserResult.getTable().getName()));
            Map<String, String> aliasAndTable = Maps.newHashMap();
            if (StringUtils.isNotBlank(parserResult.getTable().getAlias())) {
                aliasAndTable.put(parserResult.getTable().getAlias(), parserResult.getTable().getName());
            }
            if (CollectionUtils.isNotEmpty(parserResult.getJoins())) {
                parserResult.getJoins().forEach(join -> {
                    if (StringUtils.isNotBlank(join.getTable().getAlias())) {
                        aliasAndTable.put(join.getTable().getAlias(), join.getTable().getName());
                    }
                    allTableAndAlias.put(join.getTable().getName(), join.getTable().getAlias());
                    tableAndUnique.put(join.getTable().getName(), dataSourceRepo.getConstraintColumns(join.getTable().getName()));
                });
            }
            List<QueryParserResult.SelectItem> allSelectItems = project.getColumns().stream().filter(column -> column instanceof QueryParserResult.AllSelectItem).toList();
            Set<String> tableNames = Sets.newHashSet();
            final AtomicInteger num = new AtomicInteger(1);
            if (CollectionUtils.isNotEmpty(allSelectItems)) {
                allSelectItems.forEach(allSelectItem -> {
                    if (StringUtils.isNotBlank(allSelectItem.getAlias())) {
                        String tableName = aliasAndTable.get(allSelectItem.getAlias());
                        if (StringUtils.isBlank(tableName)) {
                            throw new BusinessException("别名【" + allSelectItem.getAlias() + "】未找到对应表名");
                        }
                        tableNames.add(tableName);
                    } else {
                        tableNames.addAll(allTableAndAlias.keySet());
                    }
                });
                tableNames.forEach(table -> details.addAll(buildDataModelDetailByTable(table, modelName)));
            } else {
                Map<String, List<String>> columnAndTables = Maps.newHashMap();
                Map<String, Map<String, Column>> tableAndColumns = Maps.newHashMap();
                allTableAndAlias.keySet().forEach(table -> {
                    Map<String, Column> columns = dataSourceRepo.getColumns(table);
                    tableAndColumns.put(table, columns);
                    columns.forEach((column, columnInfo) -> columnAndTables.compute(column, (k, v) -> {
                        if (v == null) {
                            v = Lists.newArrayList();
                        }
                        v.add(table);
                        return v;
                    }));
                });
                project.getColumns().forEach(column -> {
                    if (Objects.equals(column.getSelectType(), STR)) {
                        String table;
                        String columnTable = column.getTable();
                        if (StringUtils.isBlank(columnTable)) {
                            List<String> tables = columnAndTables.get(column.getItem());
                            if (CollectionUtils.isEmpty(tables)) {
                                throw new BusinessException("字段【" + column.getItem() + "】未找到对应表名");
                            }
                            if (tables.size() > 1) {
                                throw new BusinessException("字段【" + column.getItem() + "】在多个表中找到");
                            }
                            table = tables.getFirst();
                        } else {
                            table = aliasAndTable.get(columnTable);
                        }
                        Column columnMeta = tableAndColumns.get(table).get(column.getItem());
                        DataModelDetail detail = buildDataModelDetailByColumn(table, column.getItem(), columnMeta, num, modelName, tableAndUnique.get(table));
                        if (StringUtils.isNotBlank(column.getAlias())) {
                            detail.setDescription(column.getAlias());
                        }
                        details.add(detail);
                    } else {
                        String name = column.getItem();
                        DataModelDetail detail = buildDataModelDetailByColumn("", name, null, num, modelName, null);
                        if (StringUtils.isNotBlank(column.getAlias())) {
                            detail.setDescription(column.getAlias());
                        }
                        details.add(detail);
                    }
                });
            }
        } catch (JSQLParserException e) {
            log.error("解析sql失败", e);
            throw new BusinessException("解析sql失败");
        }
        return details;
    }

    public static boolean isAggTableModel(String tableType) {
        return StringUtils.equals(TABLE_TYPE_AGGREGATE, tableType);
    }

    public static boolean isSqlModel(String type) {
        return Objects.equals(type, FormConstants.MODEL_TYPE_SQL);
    }

    @JsonProperty("modelName")
    public String getModelName() {
        return this.name;
    }
}
